/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QUndoView>
#include <QtWidgets/QUndoGroup>
#include <QDebug>
#include <QSettings>
#include <QAction>
#include <QMessageBox>
#include <QModelIndex>
#include <QTimer>
#include <QListWidget>
#include <QScrollArea>
#include <QTableView>
#include <QTreeWidget>
#include <QGroupBox>
#include <QSessionManager>
#include "pybind11/pybind11.h"
#include "memorynode.h"
#include "consolestream.h"
#include "pythonmodules.h"
#include "filewatcher.h"
#include "validatornode.h"
#include "pythoninterpreterlineedit.h"


class SearchWindow;         // fwd decl
class SettingsDialog;       // fwd decl
class CustomNodesView;      // fwd decl
class CustomAttributesView; // fwd decl
class NodesModel;           // fwd decl
class AttributesModel;      // fwd decl

class PythonAction;         // fwd decl


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(bool setup_console = true, QWidget *parent = nullptr);
    ~MainWindow() override;
    void closeEvent(QCloseEvent* event) override;
    YAML::Node getSchema();
    ValidatorNode* getValidator();

    QMessageBox::StandardButton getSaveConfirmation(const QString& full_path = "");
    QMessageBox::StandardButton getSaveSubmapConfirmation(const QString& full_path = "");

    QMessageBox::StandardButton getLoadCustomSchemaConfirmation(const QFileInfo& schema_file);

    static MainWindow* getInstance();

    QString getOpenedFilename() const;
    QString getCurrentPath() const;

    bool isModified() const;
    bool hasFile() const;

    void save(const QString& fileName);
    void load(const QString& fileName, const YAML::Node& schema, ValidatorNode* validator);
    void reload();
    void newMap(ValidatorNode* validator);

    void itemChanged(MemoryNode* item);
    void itemChanged(Attribute* item);

    NodesModel* getNodesModel();
    AttributesModel* getAttributesModel(const QModelIndex& index);
    AttributesModel* getCurrentAttributesModel();

    CustomNodesView* getNodesView();

    FileWatcher* getFileWatcher();

    void commitData(QSessionManager& manager);

    void clearState();

    QUndoStack* getUndoStack();

    bool getPromptSubmap();
    void setPromptSubmap(bool);

    bool getPromptMainmap();
    void setPromptMainmap(bool);

public Q_SLOTS:
    void nodesViewCurrentChanged(const QModelIndex &index);
    void nodesViewSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
    void nodesViewModelChanged();
    void onChildrenOverviewNode_clicked(const QModelIndex &index);

    void onAttributeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
    void onNodesInserted(const QModelIndex& parent, int first, int last);
    void onNodesRemoved(const QModelIndex& parent, int first, int last);
    void onNodesMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row);

    void onAttributesInserted(AttributesModel* model, const QModelIndex& parent, int first, int last);
    void onAttributesRemoved(AttributesModel* model, const QModelIndex& parent, int first, int last);

    void on_actionOpenUndoRedoView_triggered();
    void on_actionOpenFile_triggered();
    bool openFile(QString filename, bool skip_modified_check);
    void validate();

    void openRecentFile();

    void on_actionQuit_triggered();

    void on_actionSaveFile_triggered();

    void on_actionSaveAs_triggered();

    void on_actionNewMemoryMap_triggered();

    void on_actionClean_console_triggered();

    void on_callPythonScript();
    void callPythonScript(const QAction * action);
    QMessageBox::StandardButton callPythonOnSaveAction(QAction* action);
    void callPythonOnLoadScript(MemoryNode* node);
    void callPythonOnLoadedScript();
    std::string callPythonOnBeforeLoadScript(const std::string& filename);
    void callPythonOnChangeScript(QModelIndex& index);
    void callPythonOnAttributeFileChangeScript(const QModelIndex& index);
    void callPythonOnRootFileChanged();
    void callPythonOnNodesInsertedScript(const QModelIndex& parent, int first, int last);
    void callPythonOnNodesRemovedScript(const QModelIndex& parent, int first, int last);
    void callPythonOnNodesMovedScript(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row);
    void callPythonOnAttributesInsertedScript(const QModelIndex& parent, int first, int last);
    void callPythonOnAttributesRemovedScript(const QModelIndex& parent, int first, int last);

    void on_actionReload_Python_modules_triggered();

    void on_actionFind_triggered();

    void console_contextMenuEvent(const QPoint &pos);

    void autosave();
    void dragEnterEvent(QDragEnterEvent* event) override;
    void dropEvent(QDropEvent* event) override;

    void refillAttributesList();
private:
    void saveStateAndGeometry();
    void restoreStateAndGeometry();
    void setupDocks();
    void setupConsoleDock();
    void setupConsole();
    void setupPythonInterpreterWidgetDock();
    void setupPython();
    void addPythonPath(const QString& path);
    void setupNodesView();
    void setupAttributesView();
    void setupAttributeList();
    void setupChildrenList();
    void setupOverview();
    void setupToolBar();
    void setDocksAndActionsEnabled(bool enabled);

    void verticalResizeTableViewToContents(QTableView *tableView);

    void setupGlobalSettings(const QString& filename);
    QString getOpenFileName();
    QString getSaveFileName();

    QString getFriendlyFilename(const QString& filename) const;
    QMessageBox::StandardButton saveDiscardCancel();
    void loadSchema();

    void setupUndoRedo();

    QAction* undoAction;
    QAction* redoAction;

    Ui::MainWindow *ui;
    QMenu* contextMenu;
    QUndoView *undoView;
    QUndoStack *undoStack;
    // recent files
    enum { MaxRecentFiles = 5};
    QAction *recentFileActs[MaxRecentFiles];
    void addToRecentFiles(const QString& fileName);
    void updateRecentFileActions();
    // console streamer
    std::unique_ptr<ConsoleStream> streamer;

    // schema
    bool schema_ok = false;
    YAML::Node schema;
    std::unique_ptr<ValidatorNode> validator;
    QList<QAction*> on_save_actions;
    QList<QAction*> on_exit_actions;
    QList<QAction*> on_change_actions;
    QList<QAction*> on_nodes_inserted_actions;
    QList<QAction*> on_nodes_removed_actions;
    QList<QAction*> on_nodes_moved_actions;
    QList<QAction*> on_attributes_inserted_actions;
    QList<QAction*> on_attributes_removed_actions;
    QList<QAction*> on_attributes_file_changed;
    QList<QAction*> on_root_file_changed;
    QAction* on_before_load_action = nullptr;
    QList<QAction*> on_load_actions;
    QList<QAction*> on_loaded_actions;
    QList<QAction*> python_buttons;
    std::unique_ptr<PythonModules> python_modules;
    SearchWindow* search_window;
    SettingsDialog* settings_dialog;
    QTimer* autosave_timer;
    int autosave_files_to_keep = 20;
    QString autosave_path;
    int autosave_undoStack_index = -666; // shouldn't be used by QUndoStack
public:
    static QTextEdit* console;
    static MainWindow* main_window;
    friend class SearchWindow;
    friend class SettingsDialog;
    PythonInterpreterWidget* interpreter_widget;
private Q_SLOTS:
    void on_actionAbout_triggered();
    void on_actionSettings_triggered();
    void on_actionOpen_autosave_location_triggered();
    void on_actionValidate_triggered();

    void on_actionRestore_layout_triggered();

    void on_actionOpen_schema_file_location_triggered();

    void on_actionOpen_configuration_file_location_triggered();

    void on_actionOpen_Python_scripts_location_triggered();

    void on_actionOpen_user_settings_location_triggered();

    void on_actionReload_schema_triggered();

    void on_actionClose_file_triggered();

private:
    QDockWidget* consoleDock;
    QDockWidget* pythonInterpreterWidgetDock;
//    PythonInterpreterLineEdit* pythonInterpreterLineEdit;
    CustomNodesView* nodesView;
    QDockWidget* nodesViewDock;
    CustomAttributesView* attributesView;
    QDockWidget* attributesViewDock;
    QTreeWidget* attributeList;
    QDockWidget* attributeListDock;
    QTreeWidget* childrenList;
    QDockWidget* childrenListDock;
    QScrollArea* childrenOverviewScrollArea;
    QGroupBox* childrenOverview;
    QDockWidget* childrenOverviewDock;
    FileWatcher* fileWatcher;

    bool prompt_submap;
    bool prompt_mainmap;
    // current file
    QString current_file;
    QString window_title;
};

#endif // MAINWINDOW_H
